package com.ashin.cnnic;

import java.io.UnsupportedEncodingException;
import java.util.Random;

/**
 * 国密算法sms4工具类
 * <p>
 * 对于明文前后末尾没有空格，该加解密工具没有问题，可以在解密后的明文字符串trim方法去除多余空格
 * <p>
 * 但对于明文前后有空格的加解密，对于补足位数加密，然后解密会有多余的空格问题，暂时没有解决办法
 * 
 * @author Ashin
 *
 */
public class SMS4Util {

    /**
     * 使用utf-8编码
     * 
     * @param plainText
     * @param key
     * @return
     * @throws UnsupportedEncodingException
     */
    public static byte[] encodeSMS4(String plainText, byte[] key) throws UnsupportedEncodingException {
        if (plainText == null || plainText.equals("")) {
            return null;
        }

        return encodeSMS4(plainText.getBytes("utf-8"), key);
    }

    /**
     * 不限明文长度的SMS4加密
     * 
     * @param plainText
     * @param key
     * @return
     */
    public static byte[] encodeSMS4(byte[] plainText, byte[] key) {
        byte[] newText = complement16(plainText);

        byte[] ciphertext = new byte[newText.length];

        int k = 0;
        int plainLen = newText.length;
        while (k + SMS4.BLOCK <= plainLen) {
            byte[] cellPlain = new byte[SMS4.BLOCK];
            for (int i = 0; i < SMS4.BLOCK; i++) {
                cellPlain[i] = newText[k + i];
            }
            byte[] cellCipher = encode16(cellPlain, key);
            for (int i = 0; i < cellCipher.length; i++) {
                ciphertext[k + i] = cellCipher[i];
            }

            k += SMS4.BLOCK;
        }

        return ciphertext;
    }

    /**
     * 解密，获得明文字符串
     * 
     * @param cipherText
     * @param key
     * @return
     * @throws UnsupportedEncodingException 
     */
    public static String decodeSMS4toString(byte[] cipherText, byte[] key) throws UnsupportedEncodingException {
        byte[] plainText = decodeSMS4(cipherText, key);

        return new String(plainText,"utf-8");
    }

    /**
     * 不限明文长度的SMS4解密
     * 
     * @param cipherText
     * @param key
     * @return
     */
    public static byte[] decodeSMS4(byte[] cipherText, byte[] key) {
        byte[] plainText = new byte[cipherText.length];

        int k = 0;
        int cipherLen = cipherText.length;
        while (k + SMS4.BLOCK <= cipherLen) {
            byte[] cellCipher = new byte[SMS4.BLOCK];
            for (int i = 0; i < SMS4.BLOCK; i++) {
                cellCipher[i] = cipherText[k + i];
            }
            byte[] cellPlain = decode16(cellCipher, key);
            for (int i = 0; i < cellPlain.length; i++) {
                plainText[k + i] = cellPlain[i];
            }

            k += SMS4.BLOCK;
        }

        return plainText;
    }

    /**
     * 随机生成128bit sms4 加解密key密钥
     * 
     * @return
     */
    public static byte[] sms4keyGenerator() {
        Random r = new Random();
        byte[] key = new byte[SMS4.BLOCK];
        for (int i = 0; i < SMS4.BLOCK; i++) {
            key[i] = (byte) r.nextInt(255);
        }

        return key;
    }

    /**
     * 补足字节数，使得字数数可以被16整除
     * 
     * @param plainText
     * @return
     */
    private static byte[] complement16(byte[] plainText) {
        int remainder = plainText.length % SMS4.BLOCK;
        byte[] newText = new byte[plainText.length + SMS4.BLOCK - remainder];
        for (int i = 0; i < plainText.length; i++) {
            newText[i] = plainText[i];
        }

        for (int i = 0, len = SMS4.BLOCK - remainder; i < len; i++) {
            newText[plainText.length + i] = 0;
        }

        return newText;
    }

    /**
     * 只加密16位明文
     * 
     * @param plainText
     * @param key
     * @return
     */
    private static byte[] encode16(byte[] plainText, byte[] key) {
        byte[] cipher = new byte[16];
        SMS4 sm4 = new SMS4();
        sm4.sms4(plainText, 16, key, cipher, SMS4.ENCRYPT);

        return cipher;
    }

    /**
     * 只解密16位密文
     * 
     * @param plaintext
     * @param key
     * @return
     */
    private static byte[] decode16(byte[] ciphertext, byte[] key) {
        byte[] plain = new byte[16];
        SMS4 sm4 = new SMS4();
        sm4.sms4(ciphertext, 16, key, plain, SMS4.DECRYPT);

        return plain;
    }

}
